-- util
-- create by weism
-- 界面的一些工具包

require "game/ui/base/util/Coords"
require "game/ui/base/util/Dialog"
require "game/ui/base/util/IconPath"
require "game/ui/base/util/Node"
require "game/ui/base/util/Shader"
require "game/ui/base/util/Text"
require "game/ui/base/util/Effects"
require "game/ui/base/util/Scroll"
require "game/ui/base/util/ScrollTool"
require "game/ui/base/util/SlidePanel"
require "game/ui/base/util/Panel"
require "game/ui/base/util/LongPress"
require "game/ui/base/util/ForcePress"
require "game/ui/module/hero/HeroDisplay"
require "game/ui/module/hero/ShapeChangeDisplay"
require "game/ui/form/equips/UIEquipTooltip"
require "game/ui/form/hero/UISkillTooltip"
require "game/ui/module/ship/ShipDisplay"

-- 随机发言框背景自适应样式
WORDS_FRAME_ARROW_TOP_RIGHT    = 1;        -- 显示右上部的箭头
WORDS_FRAME_ARROW_TOP_LEFT     = 2;        -- 显示左下部的箭头
WORDS_FRAME_ARROW_BOTTOM_LEFT  = 3;        -- 显示左下部的箭头
WORDS_FRAME_ARROW_BOTTOM_RIGHT = 4;        -- 显示右下部的箭头

WORDS_FRAME_APPERA_DELAY_TIME  = 0.6;      -- 延迟出现的时间
WORDS_FRAME_SURVIVAL_TIME      = 10;       -- 默认页面存在的时间

-------------------------------------------------------------------------------
--平铺纹理
--@param node 待填充的节点
--@param width 填充的宽度
--@param height 填充的高度
--@param filename 填充的纹理文件名
function fillTexture(node, width, height, filename)
    local batchNode = cc.SpriteBatchNode:create(filename);
    node:addChild(batchNode);

    local texWidth = batchNode:getTexture():getContentSize().width;
    local texHeight = batchNode:getTexture():getContentSize().height;

    for x = 0, width+texWidth, texWidth do
        for y = 0, height+texHeight, texHeight do
            local sprite = cc.Sprite:createWithTexture(batchNode:getTexture());
            sprite:setPosition(x, y);
            batchNode:addChild(sprite);
        end
    end
end


--注册道具Icon通用处理事件
--点击弹出道具详细信息
function registerItemClickEvent(icon, classId)
    local function onInfo(sender, eventType)
        if eventType == ccui.TouchEventType.ended then
            if UIMgr.getCurrentScene():isOpen("UIItemDetail") then
                UIMgr.getCurrentScene():removeFormByName("UIItemDetail");
            end

            local uiItemDetail = UIItemDetail.create(classId);
            UIMgr.getCurrentScene():addForm(uiItemDetail);
        end
    end

    icon:addTouchEventListener(onInfo);
end

----------------------------------------------------
-- 绘制星星层
-- @param starNum  需要绘制的星星数量
-- @param starPath 星星的全路径（如果路径不存在，则使用默认图片）
-- @param align    对齐方式 "right" "center" "left"（默认为center）
-- @param gapCoef  图标间隙系数（如果没有传入，则使用默认值）
-- @return 返回带有指定数量星星的Layer
function createStarLayer(starNum, starPath, align, gapCoef, starScale)
    if starNum < 1 then
        cclog("星星数量(%d)错误", starNum);
        return nil;
    end

    starScale = starScale or 1;

    -- 如果星星图片不存在，则使用默认的
    if not cc.FileUtils:getInstance():isFileExist(starPath) then
        starPath = "images/ui/symbol/star_little.png";
        cclog("星星路径不存在，使用默认图片。");
    end

    local layer = cc.Layer:create();
    layer:setName("layer");
    local imageView = ccui.ImageView:create(starPath);
    imageView:setScale(starScale);
    local size = imageView:getContentSize();
    size.height = size.height * starScale;
    size.width = size.width * starScale;

    if not gapCoef then
        gapCoef = -0.2;
    end

    -- 计算图标间隙
    local gap = gapCoef*size.width;

    for i = 1, starNum do
        local image = imageView:clone();
        local x = (i - 1)*(size.width + gap);
        image:setAnchorPoint(cc.p(0, 1));
        image:setPosition(x, size.height);
        image:setName("star"..i);
        layer:addChild(image);
    end

    local totalWidth = starNum*size.width + (starNum - 1)*gap;
    layer:setContentSize(totalWidth, size.height);
    layer:setPosition(-totalWidth/2, -size.height/2);
    if align == "right" then
        layer:setPosition(-totalWidth, -size.height/2);
    elseif align == "left" then
        layer:setPosition(0, -size.height/2);
    end
    return layer;
end

-----------------------------------------------------------
-- 绘制星星层（激活的部分亮色显示，未激活部分暗色显示）
-- @param curStar  当前星级
-- @param maxStar  最大星级
-- @param lightStarPath 亮色星星的全路径（如果路径不存在，则使用默认图片）
-- @param darkStarPath  暗色星星的全路径（如果路径不存在，则使用默认图片）
-- @param align 对齐方式 "right" "center" "left"
-- @param gapCoef  图标间隙系数（如果没有传入，则使用默认值）
-- @return 返回带有指定数量星星的Layer
function createStarLayerEx(curStar, maxStar, lightStarPath, darkStarPath, align, gapCoef, order)
    if not TestFightM.isInTest() then
        assert(curStar >= 0 and curStar <= maxStar);
    end

    -- 如果星星图片不存在，则使用默认的
    if not cc.FileUtils:getInstance():isFileExist(lightStarPath) then
        lightStarPath = "images/ui/symbol/star_little.png";
        cclog("星星路径不存在，使用默认图片。");
    end

    if not cc.FileUtils:getInstance():isFileExist(darkStarPath) then
        darkStarPath = "images/ui/symbol/star_little.png";
        cclog("星星路径不存在，使用默认图片。");
    end

    local layer = cc.Layer:create();
    layer:setName("layer");
    local darkImage = ccui.ImageView:create(darkStarPath);
    local lightImage = ccui.ImageView:create(lightStarPath);
    local size = lightImage:getContentSize();

    if not gapCoef then
        gapCoef = -0.2;
    end

    -- 间隙
    local gap = gapCoef*size.width;

    for i = 1, maxStar do
        -- 当前星级以内的亮色显示
        local image;
        if i <= curStar then
            image = lightImage:clone();
        else
            image = darkImage:clone();
        end

        local x = (i - 1)*(size.width + gap);
        image:setAnchorPoint(cc.p(0, 1));
        image:setPosition(x, size.height);
        image:setName("star"..i);
        layer:addChild(image);

        if order then
            -- 靠左的在前面
            image:setLocalZOrder(maxStar - i);
        end
    end

    local totalWidth = maxStar*size.width + (maxStar - 1)*gap;
    layer:setContentSize(totalWidth, size.height);
    layer:setPosition(-totalWidth/2, -size.height/2);

    if align == "right" then
        layer:setPosition(-totalWidth, -size.height/2);
    elseif align == "left" then
        layer:setPosition(0, -size.height/2);
    end

    return layer;
end

----------------------------------------------------
-- 绘制体力消耗
-- @param lifeCost 需要绘制的胡萝卜数量
-- @param align    对齐方式 "right" "center" "left"（默认为center）
-- @param gapCoef  图标间隙系数（如果没有传入，则使用默认值）
-- @return 返回带有指定数量星星的Layer
function createLifeLayer(lifeCost, align, gapCoef)
    if lifeCost < 1 then
        cclog("体力消耗(%d)错误", lifeCost);
        return nil;
    end

    -- 如果星星图片不存在，则使用默认的
    local lifePath = getSmallAttribIconPath("radish");

    local layer = cc.Layer:create();
    layer:setName("layer");
    local imageView = ccui.ImageView:create(lifePath);
    local size = imageView:getContentSize();

    if not gapCoef then
        gapCoef = -0.2;
    end

    -- 计算图标间隙
    local gap = gapCoef*size.width;

    for i = 1, lifeCost do
        local image = imageView:clone();
        local x = (i - 1)*(size.width + gap);
        image:setAnchorPoint(cc.p(0, 1));
        image:setPosition(x, size.height);
        image:setName("star"..i);
        layer:addChild(image);
    end

    local totalWidth = lifeCost*size.width + (lifeCost - 1)*gap;
    layer:setContentSize(totalWidth, size.height);
    layer:setPosition(-totalWidth/2, -size.height/2);
    if align == "right" then
        layer:setPosition(-totalWidth, -size.height/2);
    elseif align == "left" then
        layer:setPosition(0, -size.height/2);
    end
    return layer;
end

----------------------------------
-- 创建英雄模型
-- @param modelId     英雄模型编号
-- @param headImg     英雄图片控件
-- @param modelNode   英雄模型节点
-- @param scale       模型缩放比例
-- @param iconPath    默认头像路径
-- @param offsetX     模型横向偏移量
-- @param offsetY     模型纵向偏移量
function createHeroModel(modelId, headImg, modelNode, scale, iconPath, offsetX, offsetY, onlyImage)
    local animation = PetM.queryModel(modelId, "animation");
    if animation == "" or onlyImage == true then
        -- 未配置动画，或者仅显示图片，默认显示头像
        headImg:setVisible(true);
        headImg:loadTexture(iconPath);
    else
        -- 显示模型动画
        headImg:setVisible(false);

        local animName = PetM.queryModel(modelId, "animation");
        if animName ~= nil then
            local animaNode = HeroDisplay.create(modelId);
            animaNode:setScale(scale);

            if offsetX then
                animaNode:setPositionX(offsetX)
            end

            if offsetY then
                animaNode:setPositionY(offsetY)
            end

            modelNode:removeAllChildren();
            modelNode:addChild(animaNode);
        end
        return modelNode;
    end
end

-- 创建变身后的英雄模型
-- @param shapeId     变身编号
-- @param headImg     英雄图片控件
-- @param animName    待机动画,传入的话,隐藏图片控件
-- @param modelNode   英雄模型节点
-- @param scale       模型缩放比例
-- @param iconPath    默认头像路径
-- @param offsetX     模型横向偏移量
-- @param offsetY     模型纵向偏移量
function createShapeChangeMode(shapeId, headImg, animName, modelNode, scale, iconPath, offsetX, offsetY, onlyImage)
    if animation == "" or onlyImage == true then
        -- 未配置动画，或者仅显示图片，默认显示头像
        headImg:setVisible(true);
        headImg:loadTexture(iconPath);
    else
        -- 显示模型动画
        headImg:setVisible(false);

        if animName ~= nil then
            local animaNode = ShapeChangeDisplay.create(shapeId);
            animaNode:setScale(scale);

            if offsetX then
                animaNode:setPositionX(offsetX)
            end

            if offsetY then
                animaNode:setPositionY(offsetY)
            end

            modelNode:removeAllChildren();
            modelNode:addChild(animaNode);
        end
        return modelNode;
    end
end

------------------------
-- 显示装备悬浮
-- @param equipId  装备id
function showEquipTooltip(equipId)
    -- 获取不到装备信息，不处理
    if not EquipM.query(equipId) then
        return;
    end

    -- 显示装备详细信息
    UIMgr.getCurrentScene():removeFormByName("UIEquipTooltip");
    local uiEquipTooltip = UIEquipTooltip.create(equipId);
    UIMgr.getCurrentScene():addForm(uiEquipTooltip);
end

------------------------
-- 获取奖励描述
-- @param bonusList  奖励列表
function getBonusListDesc(bonusList)
    local attribBonus = {};
    local itemBonus = {};
    local keyList = table.keys(bonusList);

    for _, bonus in pairs(bonusList) do
        if bonus[1] == 2 then
            table.insert(attribBonus, bonus);
        elseif bonus[1] == 1 then
            table.insert(itemBonus, bonus);
        else
            -- 暂不处理
        end
    end

    local str = "";

    -- 拼接属性奖励
    local has_attrib_bonus = false;
    for _, attrib in pairs(attribBonus) do
        local fieldName = FieldsM.getFieldName(attrib[2]);
        str = str .. string.format(getLocStr("bonus_attrib_desc"), fieldName, attrib[3]);
        has_attrib_bonus = true;
    end

    if #itemBonus <= 0  then
        return str;
    end

    if has_attrib_bonus then
        str = str .. "\n";
    end

    -- 拼接物品奖励
    for _, item in pairs(itemBonus) do
        local itemName = ItemM.query(item[2], "name");
        str = str .. string.format(getLocStr("bonus_item_desc"), itemName, item[3]);
    end

    return str;
end

------------------------------
-- 设置按钮点击频率
-- @param sender  按钮对象
-- @param cdTime  冷却时间（默认0.3秒）
function setClickFrequency(sender, cdTime)
    cdTime = cdTime or 0.3;

    local function callFunc()
        if not sender then
            return;
        end

        -- 将按钮亮起来
        sender:setTouchEnabled(true);
        sender:setBright(true);
    end

    -- 限制操作频率
    sender:setTouchEnabled(false);
    sender:setBright(false);
    performWithDelay(sender, callFunc, cdTime);
end

------------------------------
-- 检查提醒类蓝色泡泡状态
-- @param hintNode  泡泡节点
-- @param para  如果为数字型，则显示数字型泡泡, 且泡泡中的数字为para值
--              如果为布尔型，则显示叹号泡泡
function checkBlueBubbleStatus(hintNode, para, scale)
    local valLabel = findChildByName(hintNode, "icon");
    TextStyleM.setTextStyle(valLabel, TextStyleM.TEXT_SIZE_SMALL, TextStyleM.TEXT_COLOR_WHITE, true);
    valLabel:getVirtualRenderer():setAdditionalKerning(getLocKerning() -3);

    cc.SpriteFrameCache:getInstance():addSpriteFrames("images/ui/BottomMenu.plist");

    local bg = findChildByName(hintNode, "bg");
    if type(para) == "number" then
        if para > 0 then
            hintNode:setVisible(true);
            bg:loadTexture("images/ui/main/hint.png", 1);
            valLabel:setVisible(true);
            valLabel:setString(tostring(para));
            playBubbleAnima(hintNode, false, scale);
        else
            hintNode:setVisible(false);
        end
    elseif type(para) == "boolean" then
        if para then
            hintNode:setVisible(true);
            bg:loadTexture("images/ui/main/hint2.png", 1);
            valLabel:setVisible(false);
            playBubbleAnima(hintNode, false, scale);
        else
            hintNode:setVisible(false);
        end
    end
end

------------------------
-- 显示技能悬浮
-- @param skillId    技能编号
-- @param requireMp  魔法值消耗
function showSkillTooltip(skillId, requireMp)
    -- 获取不到技能信息，不处理
    if not SkillM.query(skillId) then
        return;
    end

    -- 显示技能悬浮
    UIMgr.getCurrentScene():removeFormByName("UISkillTooltip");
    local uiForm = UISkillTooltip.create(skillId, requireMp);
    UIMgr.getCurrentScene():addForm(uiForm);
end

-- 显示目标选择界面蒙版
function showTargetSelectMask(targetType)
    if not UIDungeonMgr:getCurLevel() then
        return;
    end

    UIDungeonMgr:getCurLevel():showSelectTargetBg();

    -- 获取所有格子
    local grids = UIDungeonMgr:getCurLevel():getAllGrids();

    local parent = UIDungeonMgr:getCurLevel():getSelectTargetParent();
    parent:setVisible(true);

    -- 创建一个不可见的底图接收消息
    local function createPanel(parentNode, index, pos, scaleX, scaleY, offset)
        assert(type(scaleX) == 'number');
        assert(type(scaleY) == 'number');

        -- 加个不可见的底图来接收点击消息
        local imageView = ccui.ImageView:create();
        imageView:setName("target"..index);
        imageView:loadTexture("images/ui/level/blank.png")
        imageView:setScaleX(scaleX);
        imageView:setScaleY(scaleY);
        if type(offset) ~= "number" then
            offset = 0;
        else
            imageView:setLocalZOrder(1); -- 可能会被下面格子遮挡
        end
        imageView:setPosition(cc.pAdd(pos, cc.p(1, 25 + offset)));
        imageView:setTouchEnabled(true);
        AddTouchEventListener(imageView, function (sender, eventType)
            if eventType == ccui.TouchEventType.ended then
                print("select grid, index:" .. grids[index].index);
                EventMgr.fire(event.TARGET_SELECTED, grids[index].index);
            end
        end);
        parent:addChild(imageView);
    end

    -- 获取所有boss的位置
    local bossIndexs = {};
    for i = 1, #grids do
        if grids[i].gridData.type == GRID_TYPE_BOSS then
            table.insert(bossIndexs, i);
        end
    end

    -- 是否在boss格子区域
    local function isInBossGridRange(index)
        for _, bossIndex in pairs(bossIndexs) do
            if index == bossIndex then
                -- 这个格子就是boss所在格
                return true;
            end

            local classId = grids[bossIndex].gridData.monster.classId;
            local dbase = MonsterM.query(classId, "dbase");
            local clickRange = dbase["boss_click_range"];
            if type(clickRange) == 'table' then
                -- 如果配置了点击范围，判断是否在boss格的周围（目前仅支持3*3的配置，暂时先这样处理）
                if DungeonM.isAround(bossIndex, index) then
                    -- 这个格子在boss格的点击范围内
                    return true;
                end
            end
        end

        return false;
    end

    for i = 1, #grids do
        local pos = cc.p(grids[i]:getPosition());

        -- boss格的处理
        if grids[i].gridData.type == GRID_TYPE_BOSS then
            local classId = grids[i].gridData.monster.classId;
            local dbase = MonsterM.query(classId, "dbase");
            local clickRange = dbase["boss_click_range"];
            local offset = dbase["model_offset"];

            if type(clickRange) ~= 'table'  then
                -- 如果没有配置点击范围，默认为一个格子
                createPanel(parent, i, pos, 1, 1, offset);
            else
                -- 使用配置表中指定的点击范围
                createPanel(parent, i, pos, clickRange[1], clickRange[2]);
            end
        elseif not isInBossGridRange(i) then
            -- 在boss格子的点击范围内的格子无需处理

            -- 小怪默认点击区域为1格
            -- 创建一个不可见的底图接收消息
            local offset;
            if grids[i].gridData.type == GRID_TYPE_MONSTER then
                local classId = grids[i].gridData.monster.classId;
                local dbase = MonsterM.query(classId, "dbase");
                offset = dbase["model_offset"];
            end
            createPanel(parent, i, pos, 1, 1, offset);
        end
    end

    -- 需要选择目标
    if targetType ~= ATTACKER then
        for i = 1, #grids do
            if not grids[i].gridData.monster then
                grids[i]:gotoSelectState(false);
            end
        end

        -- 获取已开启的怪物格子, 重新生成怪物模型放在select_target_node上
        local openMonsterGrids = UIDungeonMgr:getCurLevel():getOpenMonsterGrids();
        for i = 1, #openMonsterGrids do
            local monsterGrid = openMonsterGrids[i];
            -- 格子上的怪物先隐藏
            monsterGrid:gotoSelectState(true);

            local worldPos = monsterGrid:convertToWorldSpace(cc.p(0, 0));

            -- 血条
            local hpWorldPos = monsterGrid.hpBarNode:convertToWorldSpace(cc.p(0, 0));
            local hpNodeScale = monsterGrid.hpBarNode:getScale();

            -- 攻击
            local atkWorldPos = monsterGrid.atkBarNode:convertToWorldSpace(cc.p(0, 0));
            local atkNodeScale = monsterGrid.atkBarNode:getScale();

            -- cd
            local cdNode = findChildByName(monsterGrid.barNode, "cd_node");
            local cdNodeWorldPos = cdNode:convertToWorldSpace(cc.p(0, 0));
            cdNodeScale = cdNode:getScale();

            local monsterNode = cc.Node:create();

            if monsterGrid.gridData.monster then
                -- 报错保护SLIMEC-1715
                local classId = monsterGrid.gridData.monster.classId;
                local changeClassId = monsterGrid.gridData.monster.dbase:query("changeClassId");

                if monsterGrid.gridData.type == GRID_TYPE_GHOST_SHIP and not changeClassId then
                    -- 幽灵船
                    createShipModel(monsterNode, classId);
                else
                    -- 普通怪物
                    local display;

                    if changeClassId then
                        display = monsterGrid:createExtraMonsterDisplay(changeClassId);
                    else
                        display = monsterGrid:createMonsterDisplay(classId);
                    end

                    monsterNode:addChild(display);

                    -- 需要停止动作
                    if monsterGrid:getIsPause() then
                        display:pauseActions();
                    end
                end

                monsterNode:setPositionY(12);

                local grid = cc.Node:create();
                grid:addChild(monsterNode);

                local dbase = MonsterM.query(classId, "dbase") or {};
                local offset = dbase["model_offset"];
                if type(offset) == 'number' then
                    monsterNode:setPositionY(12 + offset);
                end

                local toPos = parent:convertToNodeSpace(worldPos);
                grid:setPosition(toPos);

                -- 创建血量跟
                local hpBarPos = parent:convertToNodeSpace(hpWorldPos);
                local hpNode = createBarNode(monsterGrid, "hp", hpBarPos, hpNodeScale);

                -- 攻击
                local atkBarPos = parent:convertToNodeSpace(atkWorldPos);
                local atkNode = createBarNode(monsterGrid, "attack", atkBarPos, atkNodeScale);

                -- cd节点
                local cdNodePos = parent:convertToNodeSpace(cdNodeWorldPos);
                local newCdNode = createBarNode(monsterGrid, "cd", cdNodePos, cdNodeScale);

                if not openMonsterGrids[i].gridData.monster:isDead() then
                    -- 有可能怪物已经死亡
                    parent:addChild(grid);

                    -- 添加上血量
                    parent:addChild(hpNode);

                    -- 攻击
                    parent:addChild(atkNode);

                    -- cd
                    parent:addChild(newCdNode);
                end
            end
        end
    else
        -- 不需要选择目标，设置panel不交互，可以使点击任意地方可生效
        parent:setSwallowTouches(false);
    end
end

-- 隐藏目标选择界面蒙版
function hideTargetSelectMask()
    if not UIDungeonMgr:getCurLevel() then
        return;
    end

    UIDungeonMgr:getCurLevel():hideSelectTargetBg();

    -- 清除所有怪物模型
    local parent = UIDungeonMgr:getCurLevel():getSelectTargetParent();
    parent:setVisible(false);
    parent:removeAllChildren();

    -- 获取所有格子
    local grids = UIDungeonMgr:getCurLevel():getAllGrids();
    for i = 1, #grids do
        grids[i]:clearSelectState();
    end
end

-- 创建幽灵船模型
function createShipModel(modelNode, classId)
    modelNode:removeAllChildren();

    -- 创建幽灵船水流动的动画
    playEffect(modelNode, 1218);

    -- 创建模型
    local shipDisplay = ShipDisplay.create(classId);

    -- 设置缩放比例
    local scale = GhostShipM.query(classId, "model_scale");
    shipDisplay:setScale(scale);

    -- 设置偏移量
    local offsetX = GhostShipM.query(classId, "offset_x") or 0;
    local offsetY = GhostShipM.query(classId, "offset_y") or 0;

    shipDisplay:setPosition(tonumber(offsetX), tonumber(offsetY));

    modelNode:addChild(shipDisplay);
    modelNode.shipDisplay = shipDisplay;

    return shipDisplay;
end

------------------------
-- 获得随机发言的框
-- @usage
-- @param parentNode        出现随机发言的父节点
-- @param content           随机发言的内容
-- @param frameType         随机发言框的类型
-- @param survivalTime      发言框显示时时间
-- @param appearDelayTime   发言框延迟出现时间
-- @param textColor         文字颜色
function createWordsFrame(parentNode, content, frameType, survivalTime, appearDelayTime, textColor)
    local itemNode = cc.CSLoader:createNode("layout/common/DialogBox.csb");
    local widget = replaceNodeWithWidget(itemNode);
    local wordsDialog = widget:clone();
    local childBg = findChildByName(wordsDialog, "bg");
    local color = textColor or TextStyleM.TEXT_COLOR_DARK_BROWN;

    -- 设置伸缩格式
    childBg:setScale9Enabled(true);
    childBg:setCapInsets(cc.rect(30, 26, 90, 6));

    frameType = frameType or WORDS_FRAME_ARROW_BOTTOM_LEFT;

    -- 箭头的朝向
    if frameType == WORDS_FRAME_ARROW_TOP_RIGHT then
        -- 显示上部箭头
        childBg:setScaleY(-1);
    elseif frameType == WORDS_FRAME_ARROW_TOP_LEFT then
        childBg:setScaleY(-1);
        childBg:setScaleX(-1);
    elseif frameType == WORDS_FRAME_ARROW_BOTTOM_LEFT then
        -- 显示左下角箭头
        childBg:setScaleX(-1);
    end

    local wordsLabel = findChildByName(wordsDialog, "words_label");
    local kerning = -2;

    if getLang() ~= "zh" and getLang() ~= "tw" then
        kerning = -1;
    end

    TextStyleM.setTextStyle(wordsLabel, TextStyleM.TEXT_SIZE_TINY, color, false, kerning, nil, nil, 8);
    wordsLabel:setString(content);

    -- 重新设置锚点，修改位置
    if frameType == WORDS_FRAME_ARROW_TOP_RIGHT or frameType == WORDS_FRAME_ARROW_TOP_LEFT then
        childBg:setAnchorPoint(0.5, 1);
    else
        childBg:setAnchorPoint(0.5, 0);
        childBg:setPosition(childBg:getPositionX(), childBg:getPositionY() - childBg:getContentSize().height/2 - 10);
    end

    local labelHeight = wordsLabel:getContentSize().height;

    local bgHeight = labelHeight + 45;

    -- 文字很短的时候就将文本缩短
    local strWidth = wordsLabel:getContentSize().width;
    -- local strWidth = wordsLabel:getStringLength();
    local maxLineWidth = wordsLabel:getVirtualRenderer():getMaxLineWidth();

    local wordsLabelXPostion = wordsLabel:getPositionX();

    -- 最短是多少
    if strWidth <= childBg:getContentSize().width - 90 then
        strWidth = childBg:getContentSize().width - 90;
    end

    -- 调整背景的宽
    childBg:setContentSize(strWidth + 43, bgHeight);

    -- 调整文本的框的x位置
    wordsLabelXPostion = -(strWidth / 2);

    -- 适配位置
    if frameType == WORDS_FRAME_ARROW_TOP_RIGHT or frameType == WORDS_FRAME_ARROW_TOP_LEFT then
        -- 调整背景位置
        childBg:setPosition(childBg:getPositionX(), childBg:getPositionY() - bgHeight + 46);
        wordsLabel:setPosition(wordsLabelXPostion, wordsLabel:getPositionY());
    else
        -- 文本框上移
        wordsLabel:setPosition(wordsLabelXPostion, childBg:getPositionY() + bgHeight - 16);
    end

    -- 延迟发言框出现的时间
    parentNode:stopAllActions();
    parentNode:removeAllChildren();
    parentNode:addChild(wordsDialog);
    wordsDialog:setScale(0);
    local delayAmount = appearDelayTime or WORDS_FRAME_APPERA_DELAY_TIME;
    local function appearWords(wordsDialog)
        local downTime  = 0.2;
        local scaleUp = cc.ScaleTo:create(downTime, 1);
        local delayTime = cc.DelayTime:create(delayAmount);
        wordsDialog:runAction(cc.Sequence:create(delayTime, scaleUp));
    end
    appearWords(wordsDialog);

    wordsDialog:registerScriptHandler(function(event)
        if event == "exit" then
            --print("wordsDialog exit:"..content);
        end
    end);

    -- 发言框在页面中存在的时间
    local disappearTime = survivalTime or WORDS_FRAME_SURVIVAL_TIME;
    local fadeTime = 1.0;
    local function disappear()
        --print("disappear:"..content);

        -- 淡出
        local fadeOut = cc.FadeOut:create(fadeTime);
        local callFunc = cc.CallFunc:create(function()
            parentNode:removeAllChildren();
        end);

        -- 把透明度开关打开
        local wordsNode =  tolua.cast(wordsDialog,"cc.Node")
        if wordsNode then
            wordsDialog:setCascadeOpacityEnabled(true);
            wordsDialog:runAction(cc.Sequence:create(fadeOut, callFunc));
        end
    end
    performWithDelay(parentNode, disappear, disappearTime - fadeTime - 0.1);
end

------------------------
-- 创建发言框（不带箭头）
-- @usage
-- @param parentNode        发言框父节点
-- @param content           发言内容
-- @param survivalTime      发言框显示时时间
-- @param textColor         文字颜色
function createWordsFrame2(parentNode, content, survivalTime, textColor)
    local itemNode = cc.CSLoader:createNode("layout/common/DialogBox2.csb");
    local widget = replaceNodeWithWidget(itemNode);
    local wordsDialog = widget:clone();
    local childBg = findChildByName(wordsDialog, "bg");
    local color = textColor or TextStyleM.TEXT_COLOR_DARK_BROWN;
    local wordsLabel = findChildByName(wordsDialog, "words");
    local kerning = -2;

    if getLang() ~= "zh" and getLang() ~= "tw" then
        kerning = -1;
    end

    TextStyleM.setTextStyle(wordsLabel, TextStyleM.TEXT_SIZE_TINY, color, false, kerning, nil, nil, 8);
    wordsLabel:setString(content);

    -- 弹出发言框
    parentNode:stopAllActions();
    parentNode:removeAllChildren();
    parentNode:addChild(wordsDialog);
    wordsDialog:setScale(0);

    local function appearWords(wordsDialog)
        local downTime  = 0.2;
        local scaleUp = cc.ScaleTo:create(downTime, 1);
        wordsDialog:runAction(scaleUp);
    end
    appearWords(wordsDialog);

    -- 发言框存活的时间（含淡出）
    survivalTime = survivalTime or WORDS_FRAME_SURVIVAL_TIME;
    local fadeTime = 1.0;
    local function disappear()
        -- 淡出
        local fadeOut = cc.FadeOut:create(fadeTime);
        local callFunc = cc.CallFunc:create(function()
            parentNode:removeAllChildren();
        end);

        -- 把透明度开关打开
        local wordsNode =  tolua.cast(wordsDialog,"cc.Node")
        if wordsNode then
            wordsDialog:setCascadeOpacityEnabled(true);
            wordsDialog:runAction(cc.Sequence:create(fadeOut, callFunc));
        end
    end
    performWithDelay(parentNode, disappear, survivalTime - fadeTime - 0.1);
end

------------------------
-- 异步加载美术资源
-- @usage
-- @param resList        待加载的美术资源
-- @param callback       加载完毕后的回调
function loadImagesAsync(resList, callback)
    -- 列表为空，直接执行回调函数
    if #resList == 0 then
        if callback then
            callback();
        end
    end

    local finishCount = 0;
    local function onImageLoaded()
        -- 累计加载完成的个数
        finishCount = finishCount + 1;

        -- 如果全部加载完成，执行回调函数
        if finishCount == #resList then
            cclog("资源加载完毕");
            if callback then
                callback();
            end
        end
    end

    cclog("开始异步加载资源");
    for _, resPath in pairs(resList) do
        -- 异步加载列表中的资源
        cc.Director:getInstance():getTextureCache():addImageAsync(resPath, onImageLoaded);
    end
end

-- 停止滚动
function stopScrolling(textNode)
    if textNode.scrollingState == true then
        textNode.scrollingState = false;
    end
end

-- 数字滚动下降
function numScrollingDown(textNode, text, startVal, maxVal, speed, color, callback)
    local maxVal = tonumber(maxVal);

    text = text or "";

    local function scrolling()
        local increaseVal = math.abs(maxVal - startVal);

        if color then
            textNode:setTextColor(color);
        end

        -- 如果标记停止滚动了
        if textNode.scrollingState == false then
            textNode.scrollingState = nil;
            return;
        end

        textNode:setString(string.format(text, startVal));

        if startVal <= maxVal then
            textNode.scrollingState = nil;

            if callback then
                callback();
            end

            return;
        end

        -- 标记为正在滚动
        textNode.scrollingState = true;

        local num = 1;
        if increaseVal > 51 then
            num = 51;
        elseif increaseVal > 21 then
            num = 21;
        elseif increaseVal > 11 then
            num = 11;
        else
            num = 1;
        end

        if maxVal > startVal then
            startVal = startVal + num;
        else
            startVal = startVal - num;
        end

        performWithDelay(textNode, scrolling, 1 / speed);
    end

    scrolling();
end

-- 数字滚动
function numScrolling(textNode, text, startVal, maxVal, speed, color)
    local maxVal = tonumber(maxVal);

    text = text or "";

    local function scrolling()
        local increaseVal = math.abs(maxVal - startVal);

        if color then
            textNode:setTextColor(color);
        end

        -- 如果标记停止滚动了
        if textNode.scrollingState == false then
            textNode.scrollingState = nil;
            return;
        end

        textNode:setString(string.format(text, startVal));
        if startVal >= maxVal then
            textNode.scrollingState = nil;
            return;
        end

        -- 标记为正在滚动
        textNode.scrollingState = true;

        local num = 1;
        if increaseVal > 51 then
            num = 51;
        elseif increaseVal > 21 then
            num = 21;
        elseif increaseVal > 11 then
            num = 11;
        else
            num = 1;
        end

        if maxVal > startVal then
            startVal = startVal + num;
        else
            startVal = startVal - num;
        end

        performWithDelay(textNode, scrolling, 1 / speed);
    end

    scrolling();
end

------------------------------
-- 检查加号的状态
-- @param icon  icon节点
-- @param classId 固定物品的ID（如果没有则显示加号）
function checkAddBubbleStatus(icon, classId, scale)
    if icon == nil then
        return;
    end

    if classId then
        icon:loadTexture(getItemIconPath(ItemM.query(classId, "icon")));
        playBubbleAnima(icon, false, scale);
    else
        icon:loadTexture("images/ui/common/add.png");
        playBubbleAnima(icon, false, scale);
    end
end

-- 1..size 中生成一组长度为 len 的随机数列
function randomArray(size, len)
    local arr = {};
    local temp = {};

    for i = 1, size do
        table.insert(temp, i);
    end

    for i = 1, len do
        local index = math.random(#temp);
        local num = temp[index];

        table.insert(arr, num);
        table.removeItem(temp, num);
    end

    return arr;
end

-- 标题两个文本同一行时,位置居中调整
function adjustTitlePos(titleLabel, subTitleLabel, centerDistance, titleText, subText)

    -- TextStyleM.setTitleStyle(titleLabel);

    -- subTitleLabel.olSize = 3;
    -- subTitleLabel.olColor = TEXT_COLOR_TITLE_BLACK;
    -- TextStyleM.setTextStyle(subTitleLabel, TextStyleM.TEXT_SIZE_SMALL, TextStyleM.TEXT_COLOR_LIGHT_BLUE, true, -2);

    if  nil ~= titleText then
        titleLabel:setString(titleText);
    end

    if nil ~= subText then
        subTitleLabel:setString(subText);
    end

    local defaultDistance = 0;
    if getLang() ~= "zh" or getLang() ~= "tw" then
        defaultDistance = 5;
    end
    local centerDistance = centerDistance or 0;

    -- 调整下锚点
    titleLabel:setAnchorPoint(1, 0.5);
    subTitleLabel:setAnchorPoint(0, 0.5);

    -- 调整下坐标
    local titleWidth = getRealContentSize(titleLabel).width;
    local subTitleWidth = getRealContentSize(subTitleLabel).width;

    local titlePosX = (titleWidth + subTitleWidth + centerDistance + defaultDistance) / 2;

    titleLabel:setPositionX(titleWidth - titlePosX);
    subTitleLabel:setPositionX(titlePosX - subTitleWidth);
end

-- 对节点进行截屏，并将图片保存到本地
function snapshotAndSave(node, name, callback, extra)
    trace("util", "snapshotAndSave(): name = %s", name);

    -- 创建 RenderTexture，并绘制节点
    local scale = 1.0;
    if type(extra) == "table" and type(extra.scale) == "number" then
        scale = extra.scale;
        trace("util", "node scale : %d", scale);
    end
    local winSize = cc.Director:getInstance():getWinSize()
    local target = cc.RenderTexture:create(scale * winSize.width, scale * winSize.height);

    target:beginWithClear(0, 0, 0, 255);
    node:visit();
    target:endToLua();

    -- 保存图片
    target:saveToFile(name, false);

    if type(callback) == "function" then
        -- 延后一帧调用回调，这是因为保存操作不会马上执行
        -- todo:更好的作法是修改 c++ 层的代码，在保存完成后通过回调通知 lua 层
        performWithDelay(UIMgr.getCurrentScene(), callback, 0);
    end
end

----------------------------------------------------
-- 用力点击按钮中间效果，模糊屏幕并高亮当前图标
-- @param icon  icon节点
function startForcePressButtonEffect(icon)
    --获取屏幕尺寸，初始化一个空的渲染纹理对象
    local winSize = cc.Director:getInstance():getWinSize()
    local target = cc.RenderTexture:create(winSize.width, winSize.height);
    target:beginWithClear(0, 0, 0, 255);
    cc.Director:getInstance():getRunningScene():visit();
    target:endToLua();

    local sprite = cc.Sprite:createWithTexture(target:getSprite():getTexture());
    sprite:setPosition(winSize.width / 2, winSize.height / 2);
    sprite:setFlippedY(true);

    local pos = cc.p(icon:convertToWorldSpaceAR(cc.p(0,0)));

    local iconCopy = icon:clone();
    iconCopy:setPosition(pos);
    local w = AlignM.frameSize.width;
    local h = AlignM.frameSize.height;
    iconCopy:setScale(math.min(w / DESIGN_WIDTH, h / DESIGN_HEIGHT));
    sprite:addChild(iconCopy);

    setBlurMode(sprite, true)

    UIMgr:getCurrentScene():addEffect(sprite);
end

-- 屏幕模糊父节点
local blurParent = nil;

----------------------------------------------------
-- 用力点击按钮中间效果，模糊屏幕并高亮当前图标
-- @param icon  icon节点
function startForcePressButtonEffect(icon, scale)
    stopForcePressButtonEffect();

    --获取屏幕尺寸，初始化一个空的渲染纹理对象
    local winSize = cc.Director:getInstance():getWinSize()
    local target = cc.RenderTexture:create(winSize.width, winSize.height);
    target:beginWithClear(0, 0, 0, 255);
    cc.Director:getInstance():getRunningScene():visit();
    target:endToLua();

    local sprite = cc.Sprite:createWithTexture(target:getSprite():getTexture());
    sprite:setPosition(winSize.width / 2, winSize.height / 2);
    sprite:setFlippedY(true);

    local pos = cc.p(icon:convertToWorldSpaceAR(cc.p(0,0)));

    local iconCopy = icon:clone();
    iconCopy:setPosition(pos);
    local w = AlignM.frameSize.width;
    local h = AlignM.frameSize.height;
    iconCopy:setScale(scale);
    sprite:addChild(iconCopy);

    setBlurMode(sprite, true);

    blurParent = cc.Node:create();
    blurParent:addChild(sprite);

    UIMgr:getCurrentScene():addEffect(blurParent);
end

----------------------------------------------------
-- 取消模糊效果
-- @param icon  icon节点
function stopForcePressButtonEffect()
    if blurParent then
        blurParent:removeFromParent();
        blurParent = nil;
    end
end

-- 显示目标选择界面蒙版
function showSummonDieEffect(pos, callbackFunc)
    if not UIDungeonMgr:getCurLevel() then
        return;
    end

    UIDungeonMgr:getCurLevel():showSelectTargetBg();

    -- 获取所有格子
    local grids = UIDungeonMgr:getCurLevel():getAllGrids();

    local parent = UIDungeonMgr:getCurLevel():getSelectTargetParent();
    parent:setVisible(true);

    local summonGrid = grids[pos];

    local classId = summonGrid.gridData.summon.classId;

    local worldPos = summonGrid:convertToWorldSpace(cc.p(0, 0));

    local grid = cc.Node:create();

    grid.summonNode = cc.Node:create();

    -- 创建召唤兽模型
    local display;

    -- 创建模型
    local summonDisplay = SummonDisplay.create(classId);

    -- 设置缩放比例
    local scale = SummonM.query(classId, "model_scale");
    summonDisplay:setScale(scale);

    -- 设置偏移量
    local offsetX = SummonM.query(classId, "offset_x") or 0;
    local offsetY = SummonM.query(classId, "offset_y") or 0;
    summonDisplay:setPosition(tonumber(offsetX), tonumber(offsetY));

    display = summonDisplay;

    grid.summonNode:addChild(display);

    grid.summonNode:setPositionY(12);

    grid:addChild(grid.summonNode);

    local toPos = parent:convertToNodeSpace(worldPos);
    grid:setPosition(toPos);

    parent:addChild(grid);

    playShakeEffect(grid, 0.05, 10, 6);

    local function playDieAimation()
        -- 播放死亡动画
        local dieAnimFormula = SummonM.query(classId, "die_anima_formula");
        local startFrameIndex, endFrameIndex;
        if dieAnimFormula ~= nil and dieAnimFormula ~= "" then
            startFrameIndex, endFrameIndex = FormulaM.invoke(dieAnimFormula);
        end

        if dieAnimFormula == "" or (startFrameIndex == 0 and endFrameIndex == 0) then
            -- 销毁召唤兽模型
            grid:removeAllChildren();

            -- 使用默认的死亡动画
            local path = "animation/die_bomb.csb";
            local effect, animAction = AnimationCacheM.createAnimationNodeAndAction(path);
            effect:runAction(animAction);
            grid:addChild(effect);
            animAction:gotoFrameAndPlay(0, 14, false);

            local function callFunc()
                if callbackFunc then
                    callbackFunc();
                end
                grid:removeAllChildren();
                AnimationCacheM.freeAnimationNodeAndAction(path, effect, animAction);
            end

            if table.indexOf({16,17,18,19}, classId) > 0 then
                AudioM.playFx("death_effects");
            end

            performWithDelay(grid, callFunc, 0.3);
        else
            local function callback()
                -- 销毁召唤兽模型
                grid:removeAllChildren();
                if callbackFunc then
                    callbackFunc();
                end
            end

            if display then
                display:play(startFrameIndex, endFrameIndex, false, 0.5, callback);
            end
        end
    end

    performWithDelay(parent, playDieAimation, 1.8);
end

-- 获取一个节点的所有子节点
function getAllChilds(node)
    local list = {};

    local function getChilds(node)
        local childs = node:getChildren();
        if #childs == 0 then
            table.insert(list, node);
        else
            for _, c in pairs(childs) do
                getChilds(c);
            end
        end
    end
    getChilds(node);

    return list;
end

-- 遍历一个节点的所有子节点并设置它们的透明度
-- @param node    父节点
-- @param opacity 透明度
function setOpacity(node, opacity)
    local childs = node:getChildren();
    if #childs == 0 then
        node:setOpacity(opacity);
    else
        for _, child in pairs(childs) do
            setOpacity(child, opacity);
        end
    end
end
-- 发言框背景自适应
function autoResizeBg(label, bg, offsetW, offsetH, alignPos)
    local offsetWidth = offsetW or 50;
    local offsetHeight = offsetH or 50;

    local labelSize = label:getContentSize();

    -- bg的contentSize使用最原始，原始大小为最小的
    if bg.cSize == nil then
        bg.cSize = bg:getContentSize();
    end

    if label.pos == nil then
        label.pos = cc.p(label:getPosition());
    end

    -- 恢复最初的值
    local bgSize = bg.cSize;

    local offsetX = 0;
    local offsetY = 0;
    local w = bgSize.width;
    local h =  bgSize.height;

    local resizeFlag = false;

    -- 充值宽
    if  w <  labelSize.width + offsetWidth then
        -- label x的偏移量
        offsetX = (labelSize.width + offsetWidth - w) / 2;

        w = labelSize.width + offsetWidth;
    end

    if h < labelSize.height + offsetHeight then
        -- label y的偏移量
        offsetY = (labelSize.height + offsetHeight - h) / 2;

        h = labelSize.height + offsetHeight;
    end

    bg:setContentSize(w, h);

    if alignPos == true then
        local labelPos = label.pos;
        label:setPosition(cc.p(labelPos.x + offsetX, labelPos.y + offsetY));
    end
end

-- 检查是否未绑定账号的游客
function checkIfGuestNeedBind(callback)
    local gamingChannel = getLastAccountChannel() or "";
    local bindAccounts = AccountM.getBindAccounts() or {};

    if string.find(gamingChannel, "guest") and #bindAccounts == 0 then
        EventMgr.fire(event.GUEST_ACCOUNT_NEED_BIND, callback);
        return true;
    end

    callback();
    return false;
end

-- 创建天空之城boss模型
function createSkyBossMonster(monsterId, isImg)
    if isImg then
        local image = ccui.ImageView:create();
        image:loadTexture(getSkyBossImg(monsterId));

        return image;
    end

    -- 普通怪
    monsterDisplay = MonsterDisplay.create(monsterId);

    -- 设置缩放比例
    local scale = MonsterM.query(monsterId, "model_scale");
    monsterDisplay:setScale(scale);

    -- 设置偏移量
    local offsetX = MonsterM.query(monsterId, "offset_x") or 0;
    local offsetY = MonsterM.query(monsterId, "offset_y") or 0;
    monsterDisplay:setPosition(tonumber(offsetX), tonumber(offsetY));

    -- 有变红的话需要还原下颜色
    local childList = getAllChilds(monsterDisplay);

    for _, child in pairs(childList) do
        -- 图片变红
        setRedMode(child, false);
    end

    return monsterDisplay;
end

-- 显示暂停界面
local uiPause = nil;
function showPauseForm()
    if cc.Device:isInMultiWindowMode() then
        print("showPauseForm");
        require "game/ui/form/common/UIPause";
        if uiPause then
            hidePauseForm();
        end

        uiPause = UIPause.create();
        UIMgr.getCurrentScene():addEffect(uiPause);
        UIMgr.getCurrentScene():visit();
    end
end

-- 隐藏暂停界面
function hidePauseForm()
    print("hidePauseForm");
    if uiPause then
        UIMgr.getCurrentScene():removeEffect(uiPause);
        uiPause = nil;
    end
end

-- 生成游戏logo界面的屏幕快照
function createGameLogoSnapshot(imageName)
    require "game/ui/form/snapshot/UIGameSnapshot"
    local uiSnapshot = UIGameSnapshot.create();
    UIMgr.getCurrentScene():removeFormByName("UIGameSnapshot");
    UIMgr.getCurrentScene():addForm(uiSnapshot);

    -- 生成快照后关闭界面
    snapshotAndSave(uiSnapshot, imageName);
    UIMgr.getCurrentScene():removeFormByName("UIGameSnapshot");
end

-- 生成获得冈布奥界面的屏幕快照
function createGainPetSnapshot(imageName, classId)
    require "game/ui/form/snapshot/UIGainPetSnapshot"
    local uiSnapshot = UIGainPetSnapshot.create(classId);
    UIMgr.getCurrentScene():removeFormByName("UIGainPetSnapshot");
    UIMgr.getCurrentScene():addForm(uiSnapshot);

    local scale = uiSnapshot:getNodeScale();

    -- 生成快照后关闭界面
    snapshotAndSave(uiSnapshot, imageName, nil, { scale = scale, });
    UIMgr.getCurrentScene():removeFormByName("UIGainPetSnapshot");
end

-- 生成获得飞艇界面的屏幕快照
function createGainAirshipSnapshot(imageName, shipId)
    require "game/ui/form/snapshot/UIGainAirshipSnapshot"
    local uiSnapshot = UIGainAirshipSnapshot.create(shipId);
    UIMgr.getCurrentScene():removeFormByName("UIGainAirshipSnapshot");
    UIMgr.getCurrentScene():addForm(uiSnapshot);

    local scale = uiSnapshot:getNodeScale();

    -- 生成快照后关闭界面
    snapshotAndSave(uiSnapshot, imageName, nil, { scale = scale, });
    UIMgr.getCurrentScene():removeFormByName("UIGainAirshipSnapshot");
end

-- 设置标准点击效果
function setNormalPressEffect(eventType, imageNode)
    if eventType == ccui.TouchEventType.began then
        -- 1.变小
        local duration  = 0.25/2;
        local scaleDown = cc.ScaleTo:create(duration, 0.8);
        imageNode:runAction(scaleDown);
    elseif eventType == ccui.TouchEventType.ended then
        -- 2.变大
        local duration  = 0.25/2;
        local scaleUp = cc.ScaleTo:create(duration, 1);
        imageNode:runAction(scaleUp);
    elseif eventType == ccui.TouchEventType.canceled then
        -- 3.变大
        local duration  = 0.25/2;
        local scaleUp = cc.ScaleTo:create(duration, 1);
        imageNode:runAction(scaleUp);
    end
end

-- 创建状态条
function createBarNode(monsterGrid, barType, pos, nodeScale)
    local monster = monsterGrid.gridData.monster

    nodeScale = nodeScale or 1;
    local barNode = cc.Node:create();
    barNode:setPosition(pos);
    barNode:setScale(nodeScale);

    if monster == nil then
        return barNode;
    end

    local originLabel, valueLabel;

    local monsterId = monster.classId;
    local dbase = MonsterM.query(monsterId, "dbase") or {};

    local barIsVisible = false;

    if barType == "attack" then
        local atkBar = findChildByName(monsterGrid.atkBarNode, "atk_bar");

        imageView = atkBar:clone();

        originLabel = monsterGrid.attackLabel;

        valueLabel = originLabel:clone();

        barIsVisible = monsterGrid.atkBarNode:isVisible();

    elseif barType == "hp" then
        local hpBar = findChildByName(monsterGrid.hpBarNode, "hp_bar");
        imageView = hpBar:clone();

        originLabel = monsterGrid.hpLabel;
        valueLabel = originLabel:clone();

        barIsVisible = monsterGrid.hpBarNode:isVisible();

    elseif barType == "cd" then
        local cdNode = findChildByName(monsterGrid.barNode, "cd_node");
        local bg2 = findChildByName(cdNode, "bg2");
        local icon = bg2:clone();

        barNode:addChild(icon);
        local bg = findChildByName(cdNode, "bg");
        imageView = bg:clone();

        barIsVisible = cdNode:isVisible();

        -- 检查下时间停止
        if not monsterGrid.isPause then
            imageView:setVisible(true);
            icon:setVisible(false);
        else
            imageView:setVisible(false);
            icon:setVisible(true);
        end

        originLabel = findChildByName(cdNode, "cd");

        valueLabel = originLabel:clone();
    else
        return barNode;
    end

    barNode:addChild(imageView);

    valueLabel:setTextColor(originLabel:getTextColor());
    valueLabel:getVirtualRenderer():setAdditionalKerning(originLabel:getVirtualRenderer():getAdditionalKerning());
    valueLabel:getVirtualRenderer():setLineHeight(originLabel:getVirtualRenderer():getLineHeight());
    valueLabel:getVirtualRenderer():setMaxLineWidth(originLabel:getVirtualRenderer():getMaxLineWidth());
    local outlineColor = originLabel:getOutlineColor();
    local outlineSize = originLabel:getOutlineSize();
    if outlineSize ~= 0 then
        valueLabel:enableOutline(outlineColor, outlineSize);
    end

    barNode:setVisible(barIsVisible);

    barNode:addChild(valueLabel);

    return barNode;
end

-- 显示谷歌权限声明界面
function showPermissionExplainUI(callback)
    -- 查看是否已经显示过
    local hasShown = UserDefaultM.getStringForKey("permission_explain_shown");
    if hasShown ~= "1" then
        local function onOK()
            -- 设置为已显示过
            UserDefaultM.setStringForKey("permission_explain_shown", "1");

            -- 继续后续流程
            if callback then
                callback();
            end
        end

        -- 没显示过，显示权限获取声明
        confirm2(CONFIRM_TYPE_OK, getLocStr("google_center_login_title"), getLocStr("google_account_explain_desc"), onOK, nil, nil, nil,
            {["alignment_left"]=true, ["hideBackBtn"]=true});
    else
        -- 已经显示过，直接进行后续流程
        if callback then
            callback();
        end
    end
end

-----------------------------------
-- 是否显示peter的木桌
function canShowPeterDesk()
    if isOverseaPackage() then
        -- 海外的都显示
        return true;
    end

    if isIosPlatform() and PUBLISH_PLATFORM == PUBLISH_TYPE_LT then
        -- 雷霆IOS显示
        return true;
    end

    return false;
end